package mt.dummy;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import mt.Order;
import mt.comm.ClientSideMessage;
import mt.comm.ServerComm;
import mt.comm.ServerSideMessage;

/**
 * ServerCommDummy is used by the server to communicate with the clients.
 * 
 * @author Pedro,Rita, Miguel
 *
 */
public class ServerCommDummy implements ServerComm {
	/**
	 * The Port of the ServerCommDummy
	 */
	public static final int PORTO = 1901;

	/**
	 * The ServerSocket of the ServerCommDummy
	 */
	private ServerSocket serverSocket;

	/**
	 * The queue that has ServerMessage
	 */
	public BlockingQueue<ServerMessage> q = new ArrayBlockingQueue<ServerMessage>(
			100);

	/**
	 * The hashmap that has the nickname of the client and the output to answer
	 * him.
	 */

	public HashMaps<String, ObjectOutputStream> maps = new HashMaps<String, ObjectOutputStream>();

	@Override
	public void start() {
		new StartServicing().start();
	}

	/**
	 * StartServicing setups a server socket and stars another thread that
	 * listens for connections.
	 */
	class StartServicing extends Thread {

		@Override
		public void run() {
			Socket socket = null;
			try {
				serverSocket = new ServerSocket(PORTO);
				System.out.println("Server Waiting for Clients");
				while (true) {
					// Waits for clients t connect
					socket = serverSocket.accept();
					ObjectOutputStream out = new ObjectOutputStream(
							socket.getOutputStream());
					ObjectInputStream in = new ObjectInputStream(
							socket.getInputStream());
					new DealWithClient(in, out).start();
					System.out.println("A new Client has been accepted");
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * This thread listens for connections. This Threads receives and processed
	 * the ServerMessage.
	 */

	class DealWithClient extends Thread {

		private ObjectInputStream in;
		private ObjectOutputStream out;
		private String clientName;
		private boolean running;

		public DealWithClient(ObjectInputStream in, ObjectOutputStream out) {
			this.in = in;
			this.out = out;
		}

		public ObjectOutputStream getOut() {
			return out;
		}

		@Override
		public void run() {
			running = true;
			try {
				while (running) {
					ServerMessage message = (ServerMessage) in.readObject();
					if (!running) {
						break;
					}
					switch (message.getType()) {
					case CONNECTED:
						System.out.println("Quero conectar => "
								+ message.getSenderNickname());
						if (!maps.contains(message.getSenderNickname())) {
							clientName = message.getSenderNickname();
							maps.addEntry(message.getSenderNickname(), out);
							q.add(message);
						} else {
							ClientMessage msg = new ClientMessage(
									ClientSideMessage.Type.ERROR,
									"Name already exists!");
							out.writeObject(msg);
						}
						break;
					case NEW_ORDER:
						q.add(message);
						break;
					case DISCONNECTED:
						ServerMessage msg = new ServerMessage(
								ServerSideMessage.Type.DISCONNECTED,
								clientName, null);
						q.add(msg);
						break;
					default:
						break;
					}
				}
			} catch (IOException e) {
				ServerMessage msg = new ServerMessage(
						ServerSideMessage.Type.DISCONNECTED, clientName, null);
				q.add(msg);
				q.clear();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} finally {
				running = false;
			}
		}
	}

	@Override
	public boolean hasNextMessage() {
		return !q.isEmpty();
	}

	@Override
	public ServerSideMessage getNextMessage() {
		return q.poll();
	}

	@Override
	public void sendError(String toNickname, String error) {
		ObjectOutputStream out = maps.get(toNickname);
		try {
			out.writeObject(new ClientMessage(ClientSideMessage.Type.ERROR,
					error + " => " + toNickname));
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean clientIsConnected(String nickname) {
		return maps.contains(nickname);
	}

	@Override
	public void disconnectClient(String nickname) {
		maps.remove(nickname);
	}

	@Override
	public void sendOrder(String receiversNickname, Order order) {
		if (clientIsConnected(receiversNickname)) {
			ObjectOutputStream out = maps.get(receiversNickname);
			try {
				System.out.println("ServerComm Envia: " + order.toString());
				out.writeObject(new ClientMessage(ClientSideMessage.Type.ORDER,
						order));
			} catch (IOException e) {
				System.out.println("Sai do Server");
			}
		}

	}

}